﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Net.JGLI.RilSharp.Exceptions;
using System.Net;

using System.Web.Script.Serialization;
using Newtonsoft.Json;

namespace Net.JGLI.RilSharp
{
    public class RilClient
    {
        public Credentials RilCredentials { get; set; }

        public Limits ApiLimits { get; private set; }

        public RilClient(Credentials credentials)
        {
            RilCredentials = credentials;
        }

        public bool RegisterClient()
        {
            if (RilCredentials == null)
                throw new ArgumentNullException("RilCredentials", "The RilCredentials property cannot be null.");

            RilCredentials.Validate();

            var request = new ApiRequest(ApiUris.SignUp);
            var response = request.Execute(
                new[]
                    {
                        RilCredentials.UserName,
                        RilCredentials.Password,
                        RilCredentials.ApiKey
                    }
                );

            if (response.Status == 401)
                throw new UsernameAlreadyTakenException(RilCredentials.UserName);

            ValidateResponse(response);

            return response.Status == 200;

        }

        public bool Authenticate()
        {
            if (RilCredentials == null)
                throw new ArgumentNullException("RilCredentials", "The RilCredentials property cannot be null.");

            RilCredentials.Validate();

            var request = new ApiRequest(ApiUris.Authenticate);
            var response = ValidateResponse(request.Execute(
                new[]
                    {
                        RilCredentials.UserName,
                        RilCredentials.Password,
                        RilCredentials.ApiKey
                    }
                ));

            return response.Status == 200;
        }

        public Limits ApiStatistics()
        {
            if (RilCredentials == null)
                throw new ArgumentNullException("RilCredentials", "The RilCredentials property cannot be null.");

            RilCredentials.Validate();

            var request = new ApiRequest(ApiUris.Api);
            var response = ValidateResponse(request.Execute(
                new[]
                    {
                        RilCredentials.ApiKey
                    }
                ));
            if (response.Status == 200)
                return ApiLimits;

            return null;
        }

        public bool Add(string url, string title, bool autoTitle)
        {
            if (RilCredentials == null)
                throw new ArgumentNullException("RilCredentials", "The RilCredentials property cannot be null.");

            RilCredentials.Validate();

            if (autoTitle)
            {
                try
                {
                    WebClient cl = new WebClient();
                    string page = cl.DownloadString(url);
                    title = Regex.Match(page, @"\<title\b[^>]*\>\s*(?<Title>[\s\S]*?)\</title\>", RegexOptions.IgnoreCase).Groups["Title"].Value;
                }
                catch (Exception)
                {
                }
            }

            var request = new ApiRequest(ApiUris.Add);
            var response = ValidateResponse(request.Execute(
                new[]
                    { 
                        RilCredentials.UserName,
                        RilCredentials.Password,
                        RilCredentials.ApiKey,
                        url,
                        title
                    }
                ));
            return (response.Status == 200);

        }

        public object Text(string url, TextMode mode, bool isIncludeImages)
        {
            if (RilCredentials == null)
                throw new ArgumentNullException("RilCredentials", "The RilCredentials property cannot be null.");

            RilCredentials.Validate();

            var request = new ApiRequest(ApiUris.Text);
            var response = ValidateResponse(request.Execute(
                new[]
                    { 
                        RilCredentials.ApiKey,
                        url,
                        mode.ToString().ToLower(),
                        (isIncludeImages?"1":"0")
                    }
                ));

            if (response.Status == 200)
            {
                return response.Response;
            }

            return null;
        }

        public Statistics Stats()
        {
            if (RilCredentials == null)
                throw new ArgumentNullException("RilCredentials", "The RilCredentials property cannot be null.");

            RilCredentials.Validate();

            var request = new ApiRequest(ApiUris.Stats);
            var response = ValidateResponse(request.Execute(
                new[]
                    {
                        RilCredentials.UserName,
                        RilCredentials.Password,
                        RilCredentials.ApiKey
                    }
                ));
            if (response.Status == 200 && response.Response != null)
            {
                var result = JsonConvert.DeserializeObject<Statistics>(response.Response);
                return result;
            }

            return null;
        }

        public RilList Get(ReadState state, DateTime? since, int? count, int? page, bool myAppOnly, bool tags)
        {
            if (RilCredentials == null)
                throw new ArgumentNullException("RilCredentials", "The RilCredentials property cannot be null.");

            RilCredentials.Validate();

            var request = new ApiRequest(ApiUris.Get);
            var response = ValidateResponse(request.Execute(
                new[]
                    {
                        RilCredentials.UserName,
                        RilCredentials.Password,
                        RilCredentials.ApiKey,
                        (state == ReadState.All ? "" : (state).ToString().ToLower()),
                        (myAppOnly? "1":"0"),
                        (!since.HasValue  ? "": (UnixTime.FromDatetime(since.Value).ToString())),
                        (!count.HasValue ? "":count.Value.ToString()),
                        (!page.HasValue ? "":page.Value.ToString()),
                        (tags? "1":"0")
                    }
                ));
            if (response.Status == 200 && response.Response != null)
            {

                try
                {
                    var result = JsonConvert.DeserializeObject<RilList>(response.Response);
                    return result;
                }
                catch (System.Exception ex)
                {
                    return null;
                }
            }

            return null;
        }

        public bool Send(SendType type, List<RilListItem> items)
        {
            if (RilCredentials == null)
                throw new ArgumentNullException("RilCredentials", "The RilCredentials property cannot be null.");

            RilCredentials.Validate();

            var request = new ApiRequest(ApiUris.Send);
            var response = ValidateResponse(request.ExecutePost(type,
                GetPostBody(type, items),
                new[]
                    {
                        RilCredentials.UserName,
                        RilCredentials.Password,
                        RilCredentials.ApiKey
                    }
                ));
            return (response.Status == 200);

        }

        private string GetPostBody(SendType type, List<RilListItem> items)
        {

            StringBuilder sb = new StringBuilder();
            switch (type)
            {
                case SendType.New:
                    sb.Append("new={");
                    for (int i = 0; i < items.Count; i++)
                    {
                        sb.AppendFormat("\"{0}\"", i);
                        sb.Append(":{");
                        sb.AppendFormat("\"url\":\"{0}\"", items[i].Url);
                        sb.Append(",");
                        sb.AppendFormat("\"title\":\"{0}\"", items[i].Title);
                        sb.Append("}");
                        if (i == items.Count - 1)
                            sb.Append("}");
                        else
                            sb.Append(",");
                    }
                    return sb.ToString();
                    break;
                case SendType.Read:
                    sb.Append("read={");
                    for (int i = 0; i < items.Count; i++)
                    {
                        sb.AppendFormat("\"{0}\"", i);
                        sb.Append(":{");
                        sb.AppendFormat("\"url\":\"{0}\"", items[i].Url);
                        sb.Append("}");
                        if (i == items.Count - 1)
                            sb.Append("}");
                        else
                            sb.Append(",");
                    }
                    return sb.ToString();
                    break;
                case SendType.Update_title:
                    sb.Append("update_title={");
                    for (int i = 0; i < items.Count; i++)
                    {
                        sb.AppendFormat("\"{0}\"", i);
                        sb.Append(":{");
                        sb.AppendFormat("\"url\":\"{0}\"", items[i].Url);
                        sb.Append(",");
                        sb.AppendFormat("\"title\":\"{0}\"", items[i].Title);
                        sb.Append("}");
                        if (i == items.Count - 1)
                            sb.Append("}");
                        else
                            sb.Append(",");
                    }
                    return sb.ToString();
                    break;
                case SendType.Update_tags:
                    sb.Append("update_tags={");
                    for (int i = 0; i < items.Count; i++)
                    {
                        sb.AppendFormat("\"{0}\"", i);
                        sb.Append(":{");
                        sb.AppendFormat("\"url\":\"{0}\"", items[i].Url);
                        sb.Append(",");
                        sb.AppendFormat("\"tags\":\"{0}\"", items[i].Tags);
                        sb.Append("}");
                        if (i == items.Count - 1)
                            sb.Append("}");
                        else
                            sb.Append(",");
                    }
                    return sb.ToString();
                    break;
                default:
                    return "";
                    break;
            }
        }

        private ApiResponse ValidateResponse(ApiResponse response)
        {
            ApiLimits = response.LimitStatus;
            if (response.Status == 403)
                throw new LimitExceededException(response.LimitStatus);

            if (response.Status == 400)
                throw new InvalidApiRequestException(response.Error);

            if (response.Status == 401)
                throw new InvalidCredentialsException();

            return response;
        }
    }
}
